#pragma once

#include "str.h"

namespace sfc::str {

struct SearchStep {
  enum Type { Match, Reject, Done };

  Type type;
  ops::Range ids = {0, 0};

 public:
  [[sfc_inline]] operator bool() const {
    return type != Done;
  }
};

template <class P>
struct CharPredSercher {
  Str _haystack;
  P _pred;
  usize _finger = 0U;
  usize _finger_back = _haystack.len();

 public:
  auto next() -> SearchStep {
    if (_finger >= _haystack.len()) {
      return {SearchStep::Done};
    }

    const auto ch = _haystack.get_unchecked(_finger);
    const auto mm = _pred(ch) ? SearchStep::Match : SearchStep::Reject;
    const auto ss = SearchStep{mm, {_finger, _finger + 1}};
    _finger += 1;
    return ss;
  }

  auto next_back() -> SearchStep {
    if (_finger_back == 0) {
      return {SearchStep::Done};
    }

    const auto ch = _haystack.get_unchecked(_finger_back);
    const auto mm = _pred(ch) ? SearchStep::Match : SearchStep::Reject;
    const auto ss = SearchStep{mm, {_finger_back - 1, _finger_back}};
    _finger_back -= 1;
    return ss;
  }

  auto next_match() -> option::Option<ops::Range> {
    while (auto ss = this->next()) {
      if (ss.type == SearchStep::Match) {
        return ss.ids;
      }
    }
    return {};
  }

  auto next_match_back() -> option::Option<ops::Range> {
    while (auto ss = this->next_back()) {
      if (ss.type == SearchStep::Match) {
        return ss.ids;
      }
    }
    return {};
  }

  auto next_reject() -> option::Option<ops::Range> {
    while (true) {
      const auto ss = this->next();
      switch (ss.type) {
        case SearchStep::Reject: return ss.ids;
        case SearchStep::Done: return {};
        default: break;
      }
    }
    return {};
  }

  auto next_reject_back() -> option::Option<ops::Range> {
    while (true) {
      const auto ss = this->next_back();
      switch (ss.type) {
        case SearchStep::Match: return ss.ids;
        case SearchStep::Done: return {};
        default: break;
      }
    }
    return {};
  }
};

struct CharSearcher {
  Str _haystack;
  char _needle;
  usize _finger = 0U;
  usize _finger_back = _haystack.len();

 public:
  auto next() -> SearchStep {
    if (_finger >= _haystack.len()) {
      return {SearchStep::Done};
    }

    const auto ch = _haystack.get_unchecked(_finger);
    const auto mm = ch == _needle ? SearchStep::Match : SearchStep::Reject;
    const auto res = SearchStep{mm, {_finger, _finger + 1}};
    _finger += 1;
    return res;
  }

  auto next_back() -> SearchStep {
    if (_finger_back == 0) {
      return {SearchStep::Done};
    }

    const auto ch = _haystack.get_unchecked(_finger_back - 1);
    const auto mm = ch == _needle ? SearchStep::Match : SearchStep::Reject;
    const auto res = SearchStep{mm, {_finger_back - 1, _finger_back}};
    _finger_back -= 1;
    return res;
  }

  auto next_match() -> option::Option<ops::Range> {
    if (_finger >= _finger_back) {
      return {};
    }

    if (auto x = chr::memchr(_haystack.as_chars()[{_finger, _}], _needle)) {
      const auto n = x.get_unchecked();
      return ops::Range{n, n + 1};
    }

    return {};
  }

  auto next_match_back() -> option::Option<ops::Range> {
    for (; const auto ss = this->next_back();) {
      if (ss.type == SearchStep::Match) {
        return ss.ids;
      }
    }
    return {};
  }
};

struct StrSearcher {
  Str _haystack;
  Str _needle;
  usize _finger = 0;
  usize _finger_back = num::saturating_sub(_haystack.len() + 1, _needle.len());

 public:
  auto next() -> SearchStep;
  auto next_back() -> SearchStep;
};

template <class P>
struct Pattern {
  P _pred;

 public:
  auto into_searcher(Str s) && -> CharPredSercher<P> {
    return {s, static_cast<P&&>(_pred)};
  }
};

template <>
struct Pattern<char> {
  char _needle;

 public:
  auto into_searcher(Str s) const -> CharSearcher {
    return {s, _needle};
  }
};

template <>
struct Pattern<Str> {
  Str _needle;

 public:
  auto into_searcher(Str s) const -> StrSearcher {
    return {s, _needle};
  }
};

template <class P>
Pattern(P) -> Pattern<P>;

Pattern(char) -> Pattern<char>;
Pattern(const char*) -> Pattern<Str>;
Pattern(Str) -> Pattern<Str>;

auto Str::find(auto p) const -> option::Option<usize> {
  auto s = Pattern{mem::move(p)}.into_searcher(*this);
  auto t = s.next_match();
  return t.map([](auto ids) { return ids._start; });
}

auto Str::rfind(auto p) const -> option::Option<usize> {
  auto s = Pattern{mem::move(p)}.into_searcher(*this);
  auto t = s.next_match_back();
  return t.map([](auto ids) { return ids._start; });
}

auto Str::contains(auto p) const -> bool {
  auto s = Pattern{mem::move(p)}.into_searcher(*this);
  auto t = s.next_match();
  return bool(t);
}

auto Str::starts_with(auto p) const -> bool {
  auto s = Pattern{mem::move(p)}.into_searcher(*this);
  auto t = s.next();
  return t.type == SearchStep::Match;
}

auto Str::ends_with(auto p) const -> bool {
  auto s = Pattern{mem::move(p)}.into_searcher(*this);
  auto t = s.next_back();
  return t.type == SearchStep::Match;
}

auto Str::trim_start_matches(auto p) const -> Str {
  auto s = Pattern{mem::move(p)}.into_searcher(*this);
  auto i = s.next_reject().unwrap_or({0U, 0U});
  return this->get_unchecked({i._start, this->len()});
}

auto Str::trim_end_matches(auto p) const -> Str {
  auto s = Pattern{mem::move(p)}.into_searcher(*this);
  auto j = s.next_reject_back().unwrap_or({this->len(), this->len()});
  return this->get_unchecked({0, j._start});
}

auto Str::trim_matches(auto p) const -> Str {
  auto s = Pattern{mem::move(p)}.into_searcher(*this);
  auto i = s.next_reject().unwrap_or({0U, 0U});
  auto j = s.next_reject_back().unwrap_or({this->len(), this->len()});
  return this->get_unchecked({i._start, j._start});
}

}  // namespace sfc::str
